﻿using System;

using MosCraft.Interfaces;
using MosCraft.Utilities;
using MosCraft.Utilities.NoiseGenerators;
using MosCraft.Terrain.MapGen;
using MosCraft.Terrain.WorldGen;

namespace MosCraft.Terrain.ChunkProviders
{
    public static class BiomeInfo {
        public static float minHeight = 0.1f;
        public static float maxHeight = 0.3f;
        public static float temperature = 0.5f;
        public static float rainfall = 0.8f;
        public static byte topBlock = 2;
        public static byte fillerBlock = 3;
    }

    /// <summary>
    /// Alpha Code Terrain Generator
    /// </summary>
    public class ChunkProviderAlpha : IChunkGenerator
    {
        private World world;
        private FastRandom rand;
        private MapGenCaves caveGenerator;

        private double[] noise3;
        private double[] noise1;
        private double[] noise2;
        private double[] noise5;
        private double[] noise6;
        private double[] stoneNoise;
        private double[] sandNoise;
        private double[] gravelNoise;
        private float[] heightField;
        private double[] noiseField;

        private NoiseGeneratorOctaves noiseGen1;
        private NoiseGeneratorOctaves noiseGen2;
        private NoiseGeneratorOctaves noiseGen3;
        private NoiseGeneratorOctaves noiseGen4;
        private NoiseGeneratorOctaves noiseGen5;
        private NoiseGeneratorOctaves noiseGen6;
        private NoiseGeneratorOctaves noiseGen7;

        public ChunkProviderAlpha(World world, long seed)
        {
            this.world = world;
            this.rand = new FastRandom(seed);
            this.caveGenerator = new MapGenCaves();

            noiseGen1 = new NoiseGeneratorOctaves(rand, 16);
            noiseGen2 = new NoiseGeneratorOctaves(rand, 16);
            noiseGen3 = new NoiseGeneratorOctaves(rand, 8);
            noiseGen4 = new NoiseGeneratorOctaves(rand, 4);
            noiseGen5 = new NoiseGeneratorOctaves(rand, 4);
            noiseGen6 = new NoiseGeneratorOctaves(rand, 10);
            noiseGen7 = new NoiseGeneratorOctaves(rand, 16);
        }

        private void generateTerrain(int x, int z, ref byte[] blocks)
        {
            byte byte0 = 4;
            int k = world.getMaxHeight() / 8;
            int seaLevel = world.getSeaLevel();
            int i1 = byte0 + 1;
            int j1 = world.getMaxHeight() / 8 + 1;
            int k1 = byte0 + 1;

            noiseField = this.initializeNoiseField(noiseField, x * byte0, 0, z * byte0, i1, j1, k1);

            for (int l1 = 0; l1 < byte0; l1++)
            {
                for (int i2 = 0; i2 < byte0; i2++)
                {
                    for (int j2 = 0; j2 < k; j2++)
                    {
                        double d = 0.125D;
                        double d1 = noiseField[((l1 + 0) * k1 + (i2 + 0)) * j1 + (j2 + 0)];
                        double d2 = noiseField[((l1 + 0) * k1 + (i2 + 1)) * j1 + (j2 + 0)];
                        double d3 = noiseField[((l1 + 1) * k1 + (i2 + 0)) * j1 + (j2 + 0)];
                        double d4 = noiseField[((l1 + 1) * k1 + (i2 + 1)) * j1 + (j2 + 0)];
                        double d5 = (noiseField[((l1 + 0) * k1 + (i2 + 0)) * j1 + (j2 + 1)] - d1) * d;
                        double d6 = (noiseField[((l1 + 0) * k1 + (i2 + 1)) * j1 + (j2 + 1)] - d2) * d;
                        double d7 = (noiseField[((l1 + 1) * k1 + (i2 + 0)) * j1 + (j2 + 1)] - d3) * d;
                        double d8 = (noiseField[((l1 + 1) * k1 + (i2 + 1)) * j1 + (j2 + 1)] - d4) * d;

                        for (int k2 = 0; k2 < 8; k2++)
                        {
                            double d9 = 0.25D;
                            double d10 = d1;
                            double d11 = d2;
                            double d12 = (d3 - d1) * d9;
                            double d13 = (d4 - d2) * d9;
                            for (int l2 = 0; l2 < 4; l2++)
                            {
                                int i3 = l2 + l1 * 4 << 11 | 0 + i2 * 4 << 7 | j2 * 8 + k2;
                                int j3 = 1 << 7;
                                i3 -= j3;
                                double d14 = 0.25D;
                                double d15 = d10;
                                double d16 = (d11 - d10) * d14;
                                d15 -= d16;
                                for (int k3 = 0; k3 < 4; k3++)
                                {
                                    if ((d15 += d16) > 0.0D)
                                    {
                                        blocks[i3 += j3] = (byte)Block.Stone.blockID;
                                        continue;
                                    }
                                    if (j2 * 8 + k2 < seaLevel)
                                    {
                                        blocks[i3 += j3] = (byte)Block.Sand.blockID;
                                    }
                                    else
                                    {
                                        blocks[i3 += j3] = 0;
                                    }
                                }

                                d10 += d12;
                                d11 += d13;
                            }

                            d1 += d5;
                            d2 += d6;
                            d3 += d7;
                            d4 += d8;
                        }
                    }
                }
            }
        }

        private double[] initializeNoiseField(double[] noise, int i, int j, int k, int l, int i1, int j1)
        {
            if (noise == null)
            {
                noise = new double[l * i1 * j1];
            }

            if (heightField == null)
            {
                heightField = new float[25];
                for (int k1 = -2; k1 <= 2; k1++)
                {
                    for (int l1 = -2; l1 <= 2; l1++)
                    {
                        float f = 10F / QuickMath.Sqrt((float)(k1 * k1 + l1 * l1) + 0.2F);
                        heightField[k1 + 2 + (l1 + 2) * 5] = f;
                    }
                }
            }

            double d = 684.41200000000003D;
            double d1 = 684.41200000000003D;
            noise5 = noiseGen5.generateNormalizedOctaves(noise5, i, k, l, j1, 1.121D, 1.121D, 0.5D);
            noise6 = noiseGen6.generateNormalizedOctaves(noise6, i, k, l, j1, 200D, 200D, 0.5D);
            noise3 = noiseGen3.generateNoiseOctaves(noise3, i, j, k, l, i1, j1, d / 80D, d1 / 160D, d / 80D);
            noise1 = noiseGen1.generateNoiseOctaves(noise1, i, j, k, l, i1, j1, d, d1, d);
            noise2 = noiseGen2.generateNoiseOctaves(noise2, i, j, k, l, i1, j1, d, d1, d);

            i = k = 0;
            int i2 = 0;
            int j2 = 0;

            for (int k2 = 0; k2 < l; k2++)
            {
                for (int l2 = 0; l2 < j1; l2++)
                {
                    float f1 = 0.0F;
                    float f2 = 0.0F;
                    float f3 = 0.0F;
                    byte byte0 = 2;

                    for (int i3 = -byte0; i3 <= byte0; i3++)
                    {
                        for (int j3 = -byte0; j3 <= byte0; j3++)
                        {
                            float f4 = heightField[i3 + 2 + (j3 + 2) * 5] / (BiomeInfo.minHeight + 2.0F);

                            f1 += BiomeInfo.maxHeight * f4;
                            f2 += BiomeInfo.minHeight * f4;
                            f3 += f4;
                        }
                    }

                    f1 /= f3;
                    f2 /= f3;
                    f1 = f1 * 0.9F + 0.1F;
                    f2 = (f2 * 4F - 1.0F) / 8F;
                    double d2 = noise6[j2] / 8000D;

                    if (d2 < 0.0D)
                    {
                        d2 = -d2 * 0.29999999999999999D;
                    }

                    d2 = d2 * 3D - 2D;

                    if (d2 < 0.0D)
                    {
                        d2 /= 2D;
                        if (d2 < -1D)
                        {
                            d2 = -1D;
                        }
                        d2 /= 1.3999999999999999D;
                        d2 /= 2D;
                    }
                    else
                    {
                        if (d2 > 1.0D)
                        {
                            d2 = 1.0D;
                        }
                        d2 /= 8D;
                    }

                    j2++;

                    for (int k3 = 0; k3 < i1; k3++)
                    {
                        double d3 = f2;
                        double d4 = f1;
                        d3 += d2 * 0.20000000000000001D;
                        d3 = (d3 * (double)i1) / 16D;
                        double d5 = (double)i1 / 2D + d3 * 4D;
                        double d6 = 0.0D;
                        double d7 = (((double)k3 - d5) * 12D * 128D) / (double)world.getMaxHeight() / d4;

                        if (d7 < 0.0D)
                        {
                            d7 *= 4D;
                        }

                        double d8 = noise1[i2] / 512D;
                        double d9 = noise2[i2] / 512D;
                        double d10 = (noise3[i2] / 10D + 1.0D) / 2D;

                        if (d10 < 0.0D)
                        {
                            d6 = d8;
                        }
                        else if (d10 > 1.0D)
                        {
                            d6 = d9;
                        }
                        else
                        {
                            d6 = d8 + (d9 - d8) * d10;
                        }

                        d6 -= d7;

                        if (k3 > i1 - 4)
                        {
                            double d11 = (float)(k3 - (i1 - 4)) / 3F;
                            d6 = d6 * (1.0D - d11) + -10D * d11;
                        }

                        noise[i2] = d6;
                        i2++;
                    }
                }
            }

            return noise;
        }

        public void replaceBlocksForBiome(int i, int j, ref byte[] blocks)
        {
            byte seaLevel = (byte)world.getSeaLevel();
            double d = 0.03125D;
            sandNoise = noiseGen4.generateNoiseOctaves(sandNoise, i * 16, j * 16, 0.0D, 16, 16, 1, d, d, 1.0D);
            gravelNoise = noiseGen4.generateNoiseOctaves(gravelNoise, i * 16, 109.0134D, j * 16, 16, 1, 16, d, 1.0D, d);
            stoneNoise = noiseGen5.generateNoiseOctaves(stoneNoise, i * 16, j * 16, 0.0D, 16, 16, 1, d * 2D, d * 2D, d * 2D);
            for(int width = 0; width < 16; width++)
            {
                for(int depth = 0; depth < 16; depth++)
                {
                    
                    bool isSand = sandNoise[width + depth * 16] + rand.nextDouble() * 0.20000000000000001D > 0.0D;
                    bool isGravel = gravelNoise[width + depth * 16] + rand.nextDouble() * 0.20000000000000001D > 3D;
                    int i1 = (int)(stoneNoise[width + depth * 16] / 3D + 3D + rand.nextDouble() * 0.25D);
                    int j1 = -1;
                    byte TopBlock = BiomeInfo.topBlock;
                    byte fillerBlock = BiomeInfo.fillerBlock;
                    for(int height = 127; height >= 0; height--)
                    {
                        int l1 = (depth * 16 + width) * 128 + height;
                        if(height <= 0 + rand.nextInt(5))
                        {
                            blocks[l1] = (byte)Block.Bedrock.blockID;
                            continue;
                        }
                        byte block = blocks[l1];
                        if(block == 0)
                        {
                            j1 = -1;
                            continue;
                        }
                        if(block != Block.Stone.blockID)
                        {
                            continue;
                        }
                        if(j1 == -1)
                        {
                            if(i1 <= 0)
                            {
                                TopBlock = 0;
                                fillerBlock = (byte)Block.Stone.blockID;
                            } else
                            if(height >= seaLevel - 4 && height <= seaLevel + 1)
                            {
                                TopBlock = BiomeInfo.topBlock;
                                fillerBlock = BiomeInfo.fillerBlock;
                                if(isGravel)
                                {
                                    TopBlock = 0;
                                }
                                if(isGravel)
                                {
                                    fillerBlock = (byte)Block.Gravel.blockID;
                                }
                                if(isSand)
                                {
                                    TopBlock = (byte)Block.Sand.blockID;
                                }
                                if(isSand)
                                {
                                    fillerBlock = (byte)Block.Sand.blockID;
                                }
                            }
                            if(height < seaLevel && TopBlock == 0)
                            {
                                //water!!!
                                TopBlock = (byte)Block.Water.blockID;
                            }
                            j1 = i1;
                            if(height >= seaLevel - 1)
                            {
                                blocks[l1] = TopBlock;
                            } else
                            {
                                blocks[l1] = fillerBlock;
                            }
                            continue;
                        }
                        if(j1 <= 0)
                        {
                            continue;
                        }
                        j1--;
                        blocks[l1] = fillerBlock;
                        if(j1 == 0 && fillerBlock == Block.Sand.blockID)
                        {
                            j1 = rand.nextInt(4);
                            fillerBlock = (byte)Block.Sand.blockID;
                        }
                    }

                }

            }

        }

        public void Generate(ref Chunk chunk)
        {
            int x = chunk.xPosition;
            int z = chunk.zPosition;
            rand.setSeed((long)x * 0x4f9939f508L + (long)z * 0x1ef1565bd5L);

            byte[] blocks = new byte[16 * world.getMaxHeight() * 16];

            this.generateTerrain(x, z, ref blocks);
            this.replaceBlocksForBiome(x, z, ref blocks);
            caveGenerator.generate(this, world, x, z, ref blocks);

            chunk.setData(blocks);
        }

        public void Populate(int x, int z)
        {
            int absoluteX = x * 16;
            int absoluteZ = z * 16;

            rand.setSeed(world.getSeed());
            long randX = (rand.nextLong() / 2L) * 2L + 1L;
            long randY = (rand.nextLong() / 2L) * 2L + 1L;
            rand.setSeed((long)x * randX + (long)z * randY ^ world.getSeed());

            if (rand.nextInt(4) == 0)
            {
                int LocX = absoluteX + rand.nextInt(16) + 8;
                int LocY = rand.nextInt(World.MAX_Y);
                int LocZ = absoluteZ + rand.nextInt(16) + 8;
                (new WorldGenLakes(Block.Water.blockID)).generate(world, rand, LocX, LocY, LocZ);
            }

            #region Ores
            for (int i = 0; i < 20; i++)
            {
                int l5 = absoluteX + rand.nextInt(16);
                int i9 = rand.nextInt(World.MAX_Y);
                int l11 = absoluteZ + rand.nextInt(16);
                (new WorldGenMinable(Block.Dirt.blockID, 32)).generate(world, rand, l5, i9, l11);
            }

            for (int i = 0; i < 10; i++)
            {
                int i6 = absoluteX + rand.nextInt(16);
                int j9 = rand.nextInt(World.MAX_Y);
                int i12 = absoluteZ + rand.nextInt(16);
                (new WorldGenMinable(Block.Gravel.blockID, 32)).generate(world, rand, i6, j9, i12);
            }

            for (int i = 0; i < 20; i++)
            {
                int LocX = absoluteX + rand.nextInt(16);
                int LocY = rand.nextInt(World.MAX_Y);
                int LocZ = absoluteZ + rand.nextInt(16);
                (new WorldGenMinable(Block.oreCoal.blockID, 16)).generate(world, rand, LocX, LocY, LocZ);
            }

            for (int i = 0; i < 20; i++)
            {
                int k6 = absoluteX + rand.nextInt(16);
                int l9 = rand.nextInt(64);
                int k12 = absoluteZ + rand.nextInt(16);
                (new WorldGenMinable(Block.oreIron.blockID, 8)).generate(world, rand, k6, l9, k12);
            }

            for (int i = 0; i < 2; i++)
            {
                int l6 = absoluteX + rand.nextInt(16);
                int i10 = rand.nextInt(32);
                int l12 = absoluteZ + rand.nextInt(16);
                (new WorldGenMinable(Block.oreGold.blockID, 8)).generate(world, rand, l6, i10, l12);
            }

            for (int i = 0; i < 1; i++)
            {
                int j7 = absoluteX + rand.nextInt(16);
                int k10 = rand.nextInt(16);
                int j13 = absoluteZ + rand.nextInt(16);
                (new WorldGenMinable(Block.oreDiamond.blockID, 7)).generate(world, rand, j7, k10, j13);
            }
            #endregion

            for (int i = 0; i < 2; i++)
            {
                int LocX = absoluteX + rand.nextInt(16);
                int LocZ = absoluteZ + rand.nextInt(16);
                (new WorldGenTrees()).generate(world, rand, LocX, world.getHeightValue(LocX, LocZ), LocZ);
            }

            for (int i = 0; i < 10; i++)
            {
                int LocX = absoluteX + rand.nextInt(16) + 8;
                int LocY = rand.nextInt(World.MAX_Y);
                int LocZ = absoluteZ + rand.nextInt(16) + 8;
                (new WorldGenReed()).generate(world, rand, LocX, LocY, LocZ);
            }

            for(int i = 0; i < 2; i++) 
            {
                int LocX = absoluteX + rand.nextInt(16);
                int LocZ = absoluteZ + rand.nextInt(16);
                int LocY = rand.nextInt(World.MAX_Y); //world.getHeightValue(LocX, LocZ) - 1;
                (new WorldGenTreasureHut()).generate(world, rand, LocX, LocY, LocZ);
            }

            #region Flowers
            if (rand.nextInt(5) == 0)
            {
                int LocX = absoluteX + rand.nextInt(16) + 8;
                int LocY = rand.nextInt(128);
                int LocZ = absoluteZ + rand.nextInt(16) + 8;
                (new WorldGenFlowers(Block.plantRed.blockID)).generate(world, rand, LocX, LocY, LocZ);
            }

            if (rand.nextInt(2) == 0)
            {
                int LocX = absoluteX + rand.nextInt(16) + 8;
                int LocY = rand.nextInt(128);
                int LocZ = absoluteZ + rand.nextInt(16) + 8;
                (new WorldGenFlowers(Block.plantYellow.blockID)).generate(world, rand, LocX, LocY, LocZ);
            }

            #endregion

        }
    }	
}
